前幾天我們成功上傳了多張照片,但是我們的畫面有點醜醜的,所以我們今天要把它修改成更漂亮,且下面會有指標,讓我們可以知道總共有幾張照片,以及現在的位置!!
我們這次是使用第三方套件,既然有現成的,就讓我們直接使用吧! 它也有很多樣式,可以讓我們去自己使用。來源:https://github.com/zhpanvip/viewpagerindicator
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
//目前最新是1.2.1,之後如果有更新,就用最新的吧!
implementation 'com.github.zhpanvip:viewpagerindicator:1.2.1'
我們直接在fragment_add_invitation的地方新增 Indicator就好,且因為它是在FrameLayout,所以最下面的view會是最上層
<FrameLayout
android:id="@+id/fl_invitation_detail"
android:layout_width="match_parent"
android:layout_height="@dimen/image_height"
app:layout_constraintTop_toBottomOf="@id/toolbar_invitation_detail_fragment"
app:layout_constraintStart_toStartOf="parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/banner_image"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.zhpan.indicator.IndicatorView
android:id="@+id/indicator_add_invitation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:layout_margin="10dp"/>
</FrameLayout>
這邊funtion我們會再等等從相簿拿到圖片後,呼叫它
private fun setAdapterWitIndicator(list: List<Uri>){
binding.bannerImage.adapter = MultiplePhotoAdapter(list)
binding.indicatorAddInvitation.apply {
//設定沒被選擇的顏色/被選擇的顏色
setSliderColor(Color.GRAY,ContextCompat.getColor(requireContext(),R.color.pewter_blue))
setSliderWidth(resources.getDimension(R.dimen.indicator_width))
//設定模式(更多模式可以往下看)
setSlideMode(IndicatorSlideMode.SCALE)
setIndicatorStyle(IndicatorStyle.CIRCLE)
//綁定viewPager2
.setupWithViewPager(binding.bannerImage)
}
}
Mode其他樣式如下
有許多屬性可以調整,有興趣的小夥伴可以多去看看!
我們之前在從相簿拿到照片後,我們就先把它上傳到Storage了,但這不太好,若使用者三心二意一直換照片,那我們的容量就爆啦!! 所以我們會是先把它顯示在UI上,若是使用者按送出的Button之後,我們在進行儲存
private var selectedUriList: List<Uri>? = null
private val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ uri ->
if (uri.resultCode == Activity.RESULT_OK){
val selectedUri = uri.data?.clipData
if (selectedUri != null){
val list = mutableListOf<Uri>()
val account = selectedUri.itemCount
//這邊一樣透過for迴圈把我們拿到的result改成uri
for (i in 0 until account){
val model = selectedUri.getItemAt(i).uri
list.add(model)
}
selectedUriList = list
//我們等等建立funtion讓我們可以拿到type的名稱,原本我們是在viewmodel做,但現在要改在Fragment裡面先拿到file的type
getTypeFromSelectedUri(list)
//這邊就直接用我們剛剛的套件,把list傳進去,它就會幫我們生出indicator囉!
setAdapterWitIndicator(list)
}
}
}
我們直接在fragment裡面先把type拿出來,並做成list,之後再傳給viewModel
private fun getTypeFromSelectedUri(list: List<Uri>){
var typeList: MutableList<String> = mutableListOf()
for (i in 0 until list.size){
val model = Constant.getFileExtension(requireActivity(),list[i])
if (model != null) {
typeList.add(model)
}
}
selectedUriTypeList = typeList
}
原本我們是檢查轉換完的uri是否為null,來判定符不符合,但是現在我們要把它改成,只要resultLauncher拿到的不是null,就給過,因為就代表它有選了嘛 XD
private fun validDataFormAndSaveImage(): Boolean{
return when{
selectedUriList.isNullOrEmpty() ->{
showSnackBar(resources.getString(R.string.hint_select_your_image),true)
false
}
......
}
★不要全刪喔,只是把圖片check改成上面而已!
原本的方式是,我們在onClick的時候,把invitation加入到firestore,但是現在我們要修改成,當我們把照片傳上去storage,然後全部轉換成功成String後,我們直接在viewModel呼叫addInvitationToFireStore(),這樣就可以確保當全部轉換successful的時候,就呼叫傳送Invitation。
binding.btnAddInvitationFragmentSubmit ->{
if (validDataFormAndSaveImage()){
showDialog(resources.getString(R.string.please_wait))
//一樣先拿到UI的資料
val invitation = Invitation(
user_id = accountViewModel.userDetail.value!!.id,
user_name = accountViewModel.userDetail.value?.name,
user_image = accountViewModel.userDetail.value?.image,
pet_type = selectedPetType!!,
pet_type_description = binding.edAddInvitationPetTypeDescription.text.toString().trim(),
area = selectedArea!!,
date_place = binding.edAddInvitationDatePlace.text.toString().trim(),
date_time = selectedDate!!,
note = binding.edAddInvitationNote.text.toString().trim(),
update_time = Timestamp(Date(System.currentTimeMillis())),
)
//再把剛剛從相簿拿來的照片list跟invitation傳進去
selectedUriList?.let {
matchingViewModel.saveImageToFireStorage(selectedUriTypeList,
it,invitation)
}
}
}
把fragment跟activity的參數拿掉,viewModel應該只要傳入數據,並且透過livedata,讓UI觀察
//建立當圖片fail的時候,可以讓我們UI觀察,我們不用加入successful的livedata,因為當成功的時候,它就會跑AddInvitation啦
private val _saveImage_fail = MutableLiveData<String>()
val saveImage_fail: LiveData<String>
get() = _saveImage_fail
//傳入剛剛的typeList跟uriList,它們會是配對的,所以我們可以直接跑for迴圈
fun saveImageToFireStorage( typeList: List<String>,uriList: List<Uri>,invitation: Invitation) {
val newList = mutableListOf<String>()
for (i in 0 until typeList.size){
val sdf: StorageReference = FirebaseStorage.getInstance().reference.child(
Constant.PET_IMAGE + "_" + System.currentTimeMillis() + "_" + typeList[i]
)
sdf.putFile(uriList[i])
.addOnSuccessListener { it ->
it.metadata?.reference?.downloadUrl
?.addOnSuccessListener { uri ->
val uriString = uri.toString()
newList.add(uriString)
//當所有的list都跑完後,我們就把剛剛的photoList加進去啦
if (i == uriList.size-1){
val newInvitation = Invitation(
user_id = invitation.user_id,
user_name = invitation.user_name,
user_image = invitation.user_image,
pet_type = invitation.pet_type,
pet_type_description = invitation.pet_type_description,
area = invitation.area,
date_place = invitation.date_place,
date_time = invitation.date_time,
note = invitation.note,
update_time = invitation.update_time,
photoUriList = newList
)
addInvitationToFireStore(newInvitation)
}
}
?.addOnFailureListener {
_saveImage_fail.postValue(it.toString())
}
}
.addOnFailureListener {
_saveImage_fail.postValue(it.toString())
}
}
}
fun resetSaveImageState(){
_saveImage_fail.postValue(null)
}
## 5.修改addInvitationToFireStore()
基本上就是新增livedata,讓我們UI可以觀察是上傳成功了嗎? 還是沒有
private val _invitation_add_state = MutableLiveData<Boolean>()
val invitation_add_state: LiveData<Boolean>
get() = _invitation_add_state
fun addInvitationToFireStore(invitation: Invitation) {
Firebase.firestore.collection(Constant.INVITATION)
.add(invitation)
.addOnSuccessListener {
val mHashMap = HashMap<String, Any>()
mHashMap[Constant.ID] = it.id
it.update(mHashMap)
.addOnSuccessListener {
_invitation_add_state.postValue(true)
}
.addOnFailureListener {
_invitation_add_state.postValue(false)
}
}
.addOnFailureListener {
_invitation_add_state.postValue(false)
}
}
//我們也需要reset我們的livedata喔,不然會一直觀察到之前的數據
fun resetAddInvitationState(){
_invitation_add_state.postValue(null)
}
matchingViewModel.invitation_add_state.observe( viewLifecycleOwner, androidx.lifecycle.Observer {
if (it == true){
hideDialog()
showSnackBar(resources.getString(R.string.add_invitation_successful),false)
findNavController().navigate(R.id.action_addInvitationFragment_to_navigation_home)
}else{
hideDialog()
showSnackBar(resources.getString(R.string.add_invitation_fail),true)
}
matchingViewModel.resetAddInvitationState()
})
還有觀察我們的SaveImageState
matchingViewModel.saveImage_fail.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
showSnackBar(it,true)
matchingViewModel.resetSaveImageState()
})
我們原本是單張照片,現在則是多張照片,簡單!! 我們直接在各Adapter裡面的ViewHolder裡面修改原本的方式,改成以下就好!! 讓我們顯示第一張照片
item.photoUriList?.get(0)?.let { Constant.loadPetImage(it,binding.ivDashboardInvitationItemListImage) }
大功告成啦!!
那DetailFragment基本上就一樣的方式啦,這邊就不多說啦!